<template>
  <div class="viewport" ref="viewport" @scroll="scrollFn">
    <!-- 自己做一个滚动条 -->
    <div class="scroll-bar" ref="scrollBar"></div>
    <!-- 真实渲染的内容 -->
    <div
      class="scroll-list"
      ref="scrollList"
      :style="{ transform: `translate3d(0, ${offset}px, 0)` }"
    >
      <div
        v-for="item in visibleData"
        :vid="item.id"
        :key="item.id"
        ref="items"
      >
        <slot name="items" :item="item"></slot>
      </div>
    </div>
  </div>
</template>

<script>
import throttle from "lodash/throttle"
/**
 * size 每一项的高度
 * remain 可见多少个
 * items 传进来的数据
 * variable 可变高度
 */
export default {
  props: {
    size: { type: Number, default: 8 }, //每一项的高度
    remain: Number, //可见多少个
    items: Array,
    variable: Boolean,
  },
  data() {
    return { start: 0, end: this.remain, offset: 0, positions: [] };
  },
  computed: {
    //渲染三屏
    prevCount() {
      //前面预留几个
      return Math.min(this.start, this.remain);
    },
    nextCount() {
      //后面预留几个
      return Math.min(this.remain, this.items.length - this.end);
    },
    visibleData() {
      //会根据start和end自动去截取渲染
      let start = this.start - this.prevCount;
      let end = this.end + this.nextCount;
      return this.items.slice(start, end);
    },
  },
  mounted() {
    this.$refs.viewport.style.height = this.size * this.remain + "px"; //可视区域的高度
    this.$refs.scrollBar.style.height = this.size * this.items.length + "px"; //滚动条的高度

    //如果加载完毕 我需要缓存每一项的高度
    this.casheList();
    //先记录好 等一会滚动的时候 去渲染页面时获取真实的dom高度 来更新缓存的内容
    //再重新计算滚动条的高度
  },
  created(){
    //第一次不要触发
    this.scrollFn = throttle(this.handleScroll,200,{leading:false})
  },
  async updated() {
    //页面更新钩子
    //页面渲染完成之后 需要根据当前展示的数据 更新缓存的内容
    //更新完成之后拿到真实的dom
    await this.$nextTick();
    //根据当前显示的 更新缓存中的height bottom top，最后更新滚动条的高度

    let nodes = this.$refs.items;
    if (nodes && nodes.length > 0) {
      nodes.forEach((node) => {
        let { height } = node.getBoundingClientRect(); //真实的高度
        let id = node.getAttribute("vid") - 0; //数字
        // console.log(id);
        let oldHeight = this.positions[id].height;
        let heightDiff = oldHeight - height;
        if (heightDiff) {
          // console.log("heightDiff", heightDiff);
          this.positions[id].height = height;
          this.positions[id].bottom = this.positions[id].bottom - heightDiff; //底部
          for (let i = id + 1; i < this.positions.length; i++) {
            //后续的都要移动
            this.positions[i].top = this.positions[i - 1].bottom;
            this.positions[i].bottom = this.positions[i].bottom - heightDiff;
          }
        }
      });
      //只要更新过会算出滚动条的最新高度
      this.$refs.scrollBar.style.height =
        this.positions[this.positions.length - 1].bottom + "px";
      //动态极端滚动条高度
    } else {
      return;
    }
  },
  methods: {
    casheList() {
      //缓存当前项的高度和top值 还有bottom值
      this.positions = this.items.map((_, index) => ({
        height: this.size,
        top: index * this.size,
        bottom: (index + 1) * this.size,
      }));
      // console.log(this.positions);
    },
    getStartIndex(value) {
      //查找当前滚动的需要找到的值 33.5
      let start = 0; //开始
      let end = this.positions.length - 1; //结束位置
      let temp = null;
      while (start <= end) {
        let middleIndex = parseInt((start + end) / 2);
        let middleValue = this.positions[middleIndex].bottom;
        if (middleValue == value) {
          //如果直接找到了 就返回当前的下一个
          return middleIndex + 1;
        } else if (middleValue < value) {
          //右边
          start = middleIndex + 1;
        } else if (middleValue > value) {
          if (temp == null || temp > middleIndex) {
            temp = middleIndex; //找到范围
          }
          //左边
          end = middleIndex - 1;
        }
      }
      return temp;
    },
    handleScroll() {
      console.log('scroll')
      //1.应该先算出来当前滚过去多少个，当前应该从第几个开始显示
      let scrollTop = this.$refs.viewport.scrollTop;
      // console.log(scrollTop);

      if (this.variable) {
        //如果有传递variable说明要使用二分查找找到对应的记录
        this.start = this.getStartIndex(scrollTop);
        // console.log(this.start);s
        this.end = this.start + this.remain;
        this.offset = this.positions[this.start - this.prevCount]
          ? this.positions[this.start - this.prevCount].top
          : 0;
      } else {
        //2.获取当前应该从第几个开始渲染
        this.start = Math.floor(scrollTop / this.size);
        //3.计算当前结尾的位置
        this.end = this.start + this.remain; //当前可渲染区域
        //4.定位当前的可视区域 让当前渲染的内容在当前viewport的可视区域
        //this.$refs.scrollList.style.top = scrollTop + 'px'
        //让可视区域去调整偏移位置
        //如果有预留渲染 应该把这个位置再向上移动当前这么多
        this.offset = this.start * this.size - this.size * this.prevCount;
      }
    },
  },
};
</script>

<style lang="scss">
.viewport {
  overflow-y: scroll;
  border: 1px solid #ccc;
  border-radius: 10px;
  position: relative;
  .scroll-list {
    position: absolute;
    top: 0;
  }
}
</style>
